package net.pws.common.persistence.hibernate3;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.pws.common.beanutils.BeanUtils;
import net.pws.common.persistence.BatchCallback;
import net.pws.common.persistence.IPage;
import net.pws.common.persistence.Page;
import net.pws.common.persistence.UnitOfWork;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.impl.CriteriaImpl;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.util.Assert;


/**
 * 在Spring提供的HibernateTemplate基础上增加了分页查询，批量操作。
 */
public class HibernateTemplateExtension extends HibernateTemplate {
    
    private static Log log = LogFactory.getLog(HibernateTemplateExtension.class);
    
    static final class Flush4Batch implements BatchCallback {
        
        private Session session;
        
        public Flush4Batch(Session session) {
            this.session = session;
        }
        
        public void doBatch(Object object) {
            session.flush();
        }
        
    }
    
    static final class FlushAndClear4Batch implements BatchCallback {
        
        private Session session;
        
        public FlushAndClear4Batch(Session session) {
            this.session = session;
        }
        
        public void doBatch(Object object) {
            session.flush();
            session.clear();
        }
        
    }
    
    protected static final Object[] OBJECT_ARRAY_INSTANCE = new Object[] {};
    
    /**
     * Create a new HibernateTemplate instance.
     */
    public HibernateTemplateExtension() {
    }
    
    /**
     * Create a new HibernateTemplate instance.
     * 
     * @param sessionFactory
     *            SessionFactory to create Sessions
     */
    public HibernateTemplateExtension(SessionFactory sessionFactory) {
        super(sessionFactory);
    }
    
    /**
     * Create a new HibernateTemplate instance.
     * 
     * @param sessionFactory
     *            SessionFactory to create Sessions
     * @param allowCreate
     *            if a non-transactional Session should be created when no
     *            transactional Session can be found for the current thread
     */
    public HibernateTemplateExtension(SessionFactory sessionFactory,
                                      boolean allowCreate) {
        super(sessionFactory, allowCreate);
    }
    
    // -------------------------------------------------------------------------
    // Convenience finder methods for detached criteria
    // -------------------------------------------------------------------------
    
    /**
     * 对于指定的查询条件，如果查询返回多个值，抛出异常 如果有值且只有一个，返回一个object，如果没值，返回null
     */
    protected Object findUniqueByEqual(final DetachedCriteria criteria) {
        return execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Criteria executableCriteria = criteria.getExecutableCriteria(session);
                prepareCriteria(executableCriteria);
                return executableCriteria.uniqueResult();
            }
        });
    }
    
    /**
     * 判断对于指定的查询条件返回的结果是否存在相同的多条记录 可以用于以下场景 例如:用户的系统登录名不能相同
     * 
     * @param criteria
     * @return
     * @throws DataAccessException
     */
    public boolean isNotUnique(final DetachedCriteria criteria) throws DataAccessException {
        Assert.notNull(criteria, "DetachedCriteria must not be null");
        Integer result = (Integer) execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Criteria executableCriteria = criteria.getExecutableCriteria(session);
                executableCriteria.setProjection(Projections.rowCount());
                prepareCriteria(executableCriteria);
                return executableCriteria.uniqueResult();
            }
        });
        return result.intValue() > 0;
    }
    
    /**
     * @param criteria
     * @return
     * @throws DataAccessException
     */
    public List findUniqueResultByCriteria(final DetachedCriteria criteria) throws DataAccessException {
        Assert.notNull(criteria, "DetachedCriteria must not be null");
        return (List) execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Criteria executableCriteria = criteria.getExecutableCriteria(session);
                prepareCriteria(executableCriteria);
                return executableCriteria.uniqueResult();
            }
        });
    }
    
    // ***********overload findByCriteria of HibernateTemplate******************
    public List findByCriteria(final Criteria criteria) throws DataAccessException {
        return findByCriteria(criteria, -1, -1);
    }
    
    public List findByCriteria(final Criteria criteria,
                               final int firstResult,
                               final int maxResults) throws DataAccessException {
        
        Assert.notNull(criteria, "DetachedCriteria must not be null");
        return (List) execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                prepareCriteria(criteria);
                if (firstResult >= 0) {
                    criteria.setFirstResult(firstResult);
                }
                if (maxResults > 0) {
                    criteria.setMaxResults(maxResults);
                }
                return criteria.list();
            }
        });
    }
    
    // ************overload findByNamedParam of HibernateTemplate************
    public List findByNamedParam(final String queryString,
                                 final String[] paramNames,
                                 final Object[] values,
                                 final int startIndex,
                                 final int maxResultsInPage) throws DataAccessException {
        if (paramNames.length != values.length) {
            throw new IllegalArgumentException("Length of paramNames array must match length of values array");
        }
        return (List) execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Query queryObject = session.createQuery(queryString);
                prepareQuery(queryObject);
                if (values != null) {
                    for (int i = 0; i < values.length; i++) {
                        applyNamedParameterToQuery(queryObject,
                                                   paramNames[i],
                                                   values[i]);
                    }
                }
                return queryObject.setFirstResult(startIndex)
                                  .setMaxResults(maxResultsInPage)
                                  .list();
            }
        });
    }
    
    // ************overload findByNamedParam of HibernateTemplate************
    public List findByNamedParam(final String queryString, final Map parameters) throws DataAccessException {
        final List keys = new ArrayList();
        final List values = new ArrayList();
        // 根据指定的参数执行hibernate hql查询
        for (Iterator iterator = parameters.keySet().iterator(); iterator.hasNext();) {
            String key = (String) iterator.next();
            keys.add(key);
            Object value = parameters.get(key);
            values.add(value);
        }
        if (keys.size() != values.size()) {
            throw new IllegalArgumentException("Length of paramNames array must match length of values array");
        }
        return (List) execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Query queryObject = session.createQuery(queryString);
                prepareQuery(queryObject);
                if (!values.isEmpty()) {
                    for (int i = 0; i < values.size(); i++) {
                        applyNamedParameterToQuery(queryObject,
                                                   keys.get(i).toString(),
                                                   values.get(i));
                    }
                }
                return queryObject.list();
            }
        });
    }
    
    // **************overload executeXXX of HibernateTemplate******************
    
    /**
     * @param sessionCallback
     * @param objects
     * @param batchSize
     * @param hasResult
     * @return
     * @throws DataAccessException
     */
    public void execute(final UnitOfWork unitOfWork) throws DataAccessException {
        execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException,
                                                        SQLException {
                unitOfWork.execute(new HibernatePersistenceInstructionVisitor(session));
                if (unitOfWork.isAutoFlush()) {
                    session.flush();
                }
                return null;
            }
        });
    }
    
    public int executeUpdate(final String hql, final Object[] parameters) {
        return (Integer) execute(new HibernateCallback() {
            
            public Object doInHibernate(Session session) throws HibernateException,
                                                        SQLException {
                Query queryObject = session.createQuery(hql);
                prepareQuery(queryObject);
                if (parameters != null) {
                    for (int i = 0; i < parameters.length; i++) {
                        queryObject.setParameter(i, parameters[i]);
                    }
                }
                
                return queryObject.executeUpdate();
            }
        });
    }
    
    public int executeUpdate(final String hql, final Map parameters) {
        final List keys = new ArrayList();
        final List values = new ArrayList();
        // 根据指定的参数执行hibernate hql查询
        for (Iterator iterator = parameters.keySet().iterator(); iterator.hasNext();) {
            String key = (String) iterator.next();
            keys.add(key);
            Object value = parameters.get(key);
            values.add(value);
        }
        if (keys.size() != values.size()) {
            throw new IllegalArgumentException("Length of paramNames array must match length of values array");
        }
        
        return (Integer) execute(new HibernateCallback() {
            
            public Object doInHibernate(Session session) throws HibernateException,
                                                        SQLException {
                Query queryObject = session.createQuery(hql);
                prepareQuery(queryObject);
                if (!values.isEmpty()) {
                    for (int i = 0; i < values.size(); i++) {
                        applyNamedParameterToQuery(queryObject,
                                                   keys.get(i).toString(),
                                                   values.get(i));
                    }
                }
                
                return queryObject.executeUpdate();
            }
        });
    }
    
    // ************************* batchExecute ***************************
    /**
     * @param sessionCallback
     * @param objects
     * @param batchSize
     * @param hasResult
     * @return
     * @throws DataAccessException
     */
    public List batchExecute(final SessionCallback sessionCallback,
                             final Object[] objects,
                             final Class entityClazz,
                             final int batchSize,
                             final boolean hasResult) throws DataAccessException {
        return (List) execute(new HibernateCallback() {
            
            public Object doInHibernate(Session session) throws HibernateException,
                                                        SQLException {
                FlushMode previousFlushMode = session.getFlushMode();
                try {
                    session.setFlushMode(FlushMode.MANUAL);
                    
                    List ids = new ArrayList(objects.length);
                    for (int i = 0; i < objects.length; i++) {
                        Object object = objects[i];
                        Object result = sessionCallback.callSession(session,
                                                                    entityClazz,
                                                                    object);
                        if (hasResult) {
                            ids.add(result);
                        }
                        // 如果使用i而不是i+1，则i为0的时候，会进行一次session.flush和clear，
                        // 如果jdbc batch size为50，批量的batchsize为50
                        // 如果i为50的时候会触发session的flush，这个时候实际上已经调用了51次session操作
                        // 假设为save操作，也就是说已经有51条记录进入队列，如果这个时候出发session的flush
                        // 则会触发两次jdbc batchUpdate，第一次为50条，第二次为1条，第二次没有充分利用
                        // jdbc的batchsize，因此i+1的目的是保证充分利用jdbc的batchsize，条件是批量的
                        // batchsize应该为jdbc batchsize的整数倍数，session
                        // batchsize=jdbc
                        // batchsize*n
                        if ((i + 1) % batchSize == 0) {
                            session.flush();
                            session.clear();
                        }
                    }
                    session.flush();
                    session.clear();
                    return hasResult ? ids : null;
                }
                finally {
                    session.setFlushMode(previousFlushMode);
                }
            }
            
        });
    }
    
    /**
     * @param sessionCallback
     * @param objects
     * @param batchSize
     * @param hasResult
     * @return
     * @throws DataAccessException
     */
    public List batchExecute(final SessionCallback sessionCallback,
                             final Collection objects,
                             final Class entityClazz,
                             final int batchSize,
                             final boolean hasResult) throws DataAccessException {
        return (List) execute(new HibernateCallback() {
            
            public Object doInHibernate(Session session) throws HibernateException,
                                                        SQLException {
                FlushMode previousFlushMode = session.getFlushMode();
                try {
                    session.setFlushMode(FlushMode.MANUAL);
                    List ids = new ArrayList();
                    // 使用0而不使用1是为了和数组循环模式保持一致
                    int i = 0;
                    for (Iterator iteratro = objects.iterator(); iteratro.hasNext(); i++) {
                        Object object = (Object) iteratro.next();
                        Object result = sessionCallback.callSession(session,
                                                                    entityClazz,
                                                                    object);
                        if (hasResult) {
                            ids.add(result);
                        }
                        if ((i + 1) % batchSize == 0) {
                            session.flush();
                            session.clear();
                        }
                    }
                    session.flush();
                    session.clear();
                    return hasResult ? ids : null;
                }
                finally {
                    session.setFlushMode(previousFlushMode);
                }
            }
            
        });
    }
    
    // ********************* find page **********************
    
    /**
     * 查找，返回结果为Page类型
     * 
     * @param action
     * @param exposeNativeSession
     * @return
     * @throws DataAccessException
     */
    public IPage executeFindPage(HibernateCallback action) throws DataAccessException {
        Object result = execute(action);
        if (result != null && !(result instanceof Page)) {
            throw new InvalidDataAccessApiUsageException("Result object returned from HibernateCallback isn't a Page: [" + result
                                                         + "]");
        }
        return (IPage) result;
    }
    
    /**
     * 分页查询
     * 
     * @param clazz
     * @param startPageNo
     *            起始页，页面索引采用了和Java数组索引一致的方式，也就是首页的索引为0
     * @param maxResultsInPage
     *            页面所包含数据的记录数
     * @return
     * @throws DataAccessException
     */
    public IPage queryByPagination(final Class clazz,
                                   final int startPageNo,
                                   final int maxResultsInPage) throws DataAccessException {
        return executeFindPage(new HibernateCallback() {
            
            public Object doInHibernate(Session session) throws HibernateException,
                                                        SQLException {
                try {
                    Criteria criteria = session.createCriteria(clazz);
                    return queryByPage(criteria, startPageNo, maxResultsInPage);
                }
                catch (Exception e) {
                    log.error(e);
                    throw new RuntimeException(e);
                }
            }
            
        });
    }
    
    /**
     * @param criteria
     * @param startPageNo
     * @param maxResultsInPage
     * @return
     * @throws DataAccessException
     */
    public IPage queryByPagination(final Criteria criteria,
                                   final int startPageNo,
                                   final int maxResultsInPage) throws DataAccessException {
        // 因为DetachedCriteria.getExecutableCriteria(Session session)的内部代码为
        // impl.setSession( ( SessionImplementor ) session );
        // 如果为false，则HibernateTemplate将调用createSessionProxy(session)
        // 这样将无法将session强制转化为SessionImplementor，会导致异常。
        // 因此必须调用 getHibernateTemplate().execute(action,true);
        // 而不是getHibernateTemplate().execute(action);
        return executeFindPage(new HibernateCallback() {
            
            public Object doInHibernate(Session session) throws HibernateException,
                                                        SQLException {
                try {
                    return queryByPage(criteria, startPageNo, maxResultsInPage);
                }
                catch (Exception e) {
                    log.error(e);
                    throw new RuntimeException(e);
                }
            }
            
        });
    }
    
    /**
     * @param criteria
     * @param startPageNo
     * @param maxResultsInPage
     * @return
     * @throws IllegalAccessException
     * @throws NoSuchFieldException
     */
    private Object queryByPage(Criteria criteria,
                               final int startPageNo,
                               final int maxResultsInPage) throws IllegalAccessException,
                                                          NoSuchFieldException {
        // 注意下面的代码和Hibernate的内部实现耦合
        CriteriaImpl criteriaImpl = (CriteriaImpl) criteria;
        
        // 将原有的Projection和Order条件保存并暂时清空,这样查询的时候可以提高速度
        Projection projectionHolder = criteriaImpl.getProjection();
        List orderEntriesHolder = (List) BeanUtils.getDeclaredProperty(criteriaImpl,
                                                                       "orderEntries");
        BeanUtils.setDeclaredProperty(criteriaImpl,
                                      "orderEntries",
                                      new ArrayList());
        // 获取记录总数
        long totalCount = ((Integer) criteriaImpl.setProjection(Projections.rowCount())
                                                 .uniqueResult()).longValue();
        // 恢复原有的Projection和Order条件
        criteriaImpl.setProjection(projectionHolder);
        if (projectionHolder == null) {
            criteriaImpl.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
        }
        BeanUtils.setDeclaredProperty(criteriaImpl,
                                      "orderEntries",
                                      orderEntriesHolder);
        // 如果记录总数小于1则返回空Page
        if (totalCount < 1)
            return new Page(0, 0, maxResultsInPage, Collections.EMPTY_LIST);
        // 真实的查询
        int firstResult = Page.getStartOfPage(startPageNo, maxResultsInPage);
        
        prepareCriteria(criteriaImpl);
        if (firstResult >= 0) {
            criteriaImpl.setFirstResult(firstResult);
        }
        if (maxResultsInPage > 0) {
            criteriaImpl.setMaxResults(maxResultsInPage);
        }
        List list = criteriaImpl.list();
        // 构造Page并返回
        return new Page(startPageNo, totalCount, maxResultsInPage, list);
    }
    
    /**
     * 分页查询，需要使用者提供计数的hql和用于实际查询的hql
     * 
     * @param hql4Count
     *            用于计数
     * @param hql4Query
     *            用于查询
     * @param parameters
     *            参数
     * @param startPageNo
     *            起始页
     * @param maxResultsInPage
     *            每页最大记录数
     * @return
     */
    public IPage queryByPage(final String hql4Count,
                             final String hql4Query,
                             final Map parameters,
                             final int startPageNo,
                             final int maxResultsInPage,
                             final boolean enableArray2Map) {
        Assert.hasText(hql4Count, "用于计数的hql不能为空!");
        Assert.hasText(hql4Query, "用于查询的hql不能为空！");
        Assert.isTrue(startPageNo >= 0, "起始页不能小于0！");
        return executeFindPage(new HibernateCallback() {
            
            public Object doInHibernate(Session session) throws HibernateException {
                List keys = new ArrayList();
                List values = new ArrayList();
                // 根据指定的参数执行hibernate hql查询
                for (Iterator iterator = parameters.keySet().iterator(); iterator.hasNext();) {
                    String key = (String) iterator.next();
                    keys.add(key);
                    Object value = parameters.get(key);
                    values.add(value);
                }
                List countlist = findByNamedParam(hql4Count,
                                                  (String[]) keys.toArray(new String[] {}),
                                                  (Object[]) values.toArray());
                if (countlist.size() == 0) {
                    return new Page(0,
                                    0,
                                    maxResultsInPage,
                                    Collections.EMPTY_LIST);
                }
                long totalCount = ((Long) countlist.get(0)).longValue();
                if (totalCount < 1) {// 如果记录总数小于1则返回空的Page
                    return new Page(0,
                                    0,
                                    maxResultsInPage,
                                    Collections.EMPTY_LIST);
                }
                // 计算起始页的第一条记录在数据库中的索引
                int startIndex = Page.getStartOfPage(startPageNo,
                                                     maxResultsInPage);
                List resultList = findByNamedParam(hql4Query,
                                                   (String[]) keys.toArray(new String[] {}),
                                                   (Object[]) values.toArray(),
                                                   startIndex,
                                                   maxResultsInPage);
                
                if (enableArray2Map) {
                    resultList = HibernateUtils.convertArrayListToMapList(session,
                                                                          hql4Query,
                                                                          resultList);
                }
                
                return new Page(startPageNo,
                                totalCount,
                                maxResultsInPage,
                                resultList);
            }
            
        });
    }
    
    public List findByNamedParam(final String hql4Query,
                                 final Map parameters,
                                 final boolean enableArray2Map) {
        return executeFind(new HibernateCallback() {
            
            public Object doInHibernate(Session session) throws HibernateException,
                                                        SQLException {
                List resultList = findByNamedParam(hql4Query, parameters);
                if (enableArray2Map) {
                    resultList = HibernateUtils.convertArrayListToMapList(session,
                                                                          hql4Query,
                                                                          resultList);
                }
                return resultList;
            }
        });
        
    }
    
    /**
     * 分页查询，需要使用者提供计数的hql和用于实际查询的hql
     * 
     * @param hql4Count
     *            用于计数
     * @param hql4Query
     *            用于查询
     * @param parameters
     *            参数
     * @param startPageNo
     *            起始页
     * @param maxResultsInPage
     *            每页最大记录数
     * @return
     */
    public IPage queryByPage(final String hql4Count,
                             final String hql4Query,
                             final Map parameters,
                             final int startPageNo,
                             final int maxResultsInPage) {
        return queryByPage(hql4Count,
                           hql4Query,
                           parameters,
                           startPageNo,
                           maxResultsInPage,
                           false);
    }
    
    /**
     * 分页查询，需要使用者提供计数的hql和用于实际查询的hql
     * 
     * @param hql4Count
     *            用于计数
     * @param hql4Query
     *            用于查询
     * @param parameters
     *            参数
     * @param startPageNo
     *            起始页
     * @param maxResultsInPage
     *            每页最大记录数
     * @return
     */
    public IPage queryByPage(final String hql4Count,
                             final String hql4Query,
                             final Object[] parameters,
                             final int startPageNo,
                             final int maxResultsInPage,
                             final boolean convertArray2Map) {
        Assert.hasText(hql4Count, "用于计数的hql不能为空!");
        Assert.hasText(hql4Query, "用于查询的hql不能为空！");
        Assert.isTrue(startPageNo >= 0, "起始页不能小于0！");
        return executeFindPage(new HibernateCallback() {
            
            public Object doInHibernate(Session session) throws HibernateException {
                // 根据指定的参数执行hibernate hql查询
                List countlist = find(hql4Count, parameters);
                long totalCount = ((Long) countlist.get(0)).longValue();
                // 如果记录总数小于1则返回空的Page
                if (totalCount < 1)
                    return new Page(0,
                                    0,
                                    maxResultsInPage,
                                    Collections.EMPTY_LIST);
                int startIndex = Page.getStartOfPage(startPageNo,
                                                     maxResultsInPage);
                Query query = getSession().createQuery(hql4Query);
                for (int i = 0; i < parameters.length; i++) {
                    query.setParameter(i, parameters[i]);
                }
                List list = query.setFirstResult(startIndex)
                                 .setMaxResults(maxResultsInPage)
                                 .list();
                
                if (convertArray2Map) {
                    list = HibernateUtils.convertArrayListToMapList(session,
                                                                    hql4Query,
                                                                    list);
                }
                
                return new Page(startPageNo, totalCount, maxResultsInPage, list);
            }
            
        });
    }
    
    /**
     * 分页查询，需要使用者提供计数的hql和用于实际查询的hql
     * 
     * @param hql4Count
     *            用于计数
     * @param hql4Query
     *            用于查询
     * @param parameters
     *            参数
     * @param startPageNo
     *            起始页
     * @param maxResultsInPage
     *            每页最大记录数
     * @return
     */
    public IPage queryByPage(final String hql4Count,
                             final String hql4Query,
                             final Object[] parameters,
                             final int startPageNo,
                             final int maxResultsInPage) {
        return queryByPage(hql4Count,
                           hql4Query,
                           parameters,
                           startPageNo,
                           maxResultsInPage,
                           false);
    }
    
    /**
     * 通用的分页查询,支持hql语句。使用者无需提供计数的hql，本方法会自动生成计数的hql，但是不适合复杂的hql（例如select的值带子查询）
     * 
     * @param hql4Query
     *            hql语句
     * @param parameters
     *            hql的参数
     * @param startPageNo
     *            起始页，页面索引采用了和Java数组索引一致的方式，也就是首页的索引为0
     * @param maxResultsInPage
     *            页面所包含数据的记录数
     * @return
     */
    public IPage queryByPage(final String hql4Query,
                             final Map parameters,
                             final int startPageNo,
                             final int maxResultsInPage,
                             final boolean enableArray2Map) {
        return queryByPage(getHql4Count(hql4Query),
                           hql4Query,
                           parameters,
                           startPageNo,
                           maxResultsInPage,
                           enableArray2Map);
    }
    
    /**
     * 通用的分页查询,支持hql语句。使用者无需提供计数的hql，本方法会自动生成计数的hql，但是不适合复杂的hql（例如select的值带子查询）
     * 
     * @param hql4Query
     *            hql语句
     * @param parameters
     *            hql的参数
     * @param startPageNo
     *            起始页，页面索引采用了和Java数组索引一致的方式，也就是首页的索引为0
     * @param maxResultsInPage
     *            页面所包含数据的记录数
     * @return
     */
    public IPage queryByPage(final String hql4Query,
                             final Map parameters,
                             final int startPageNo,
                             final int maxResultsInPage,
                             final boolean enableArray2Map,
                             final String keyField) {
        return queryByPage(getHql4Count(hql4Query),
                           hql4Query,
                           parameters,
                           startPageNo,
                           maxResultsInPage,
                           enableArray2Map);
    }
    
    /**
     * 通用的分页查询,支持hql语句。使用者无需提供计数的hql，本方法会自动生成计数的hql，但是不适合复杂的hql（例如select的值带子查询）
     * 
     * @param hql4Query
     *            hql语句
     * @param parameters
     *            hql的参数
     * @param startPageNo
     *            起始页，页面索引采用了和Java数组索引一致的方式，也就是首页的索引为0
     * @param maxResultsInPage
     *            页面所包含数据的记录数
     * @return
     */
    public IPage queryByPage(final String hql4Query,
                             final Map parameters,
                             final int startPageNo,
                             final int maxResultsInPage) {
        return queryByPage(getHql4Count(hql4Query),
                           hql4Query,
                           parameters,
                           startPageNo,
                           maxResultsInPage,
                           false);
    }
    
    /**
     * 通用的分页查询,支持hql语句。使用者无需提供计数的hql，本方法会自动生成计数的hql，但是不适合复杂的hql（例如select的值带子查询）
     * 
     * @param hql4Query
     *            hql语句
     * @param parameters
     *            hql的参数
     * @param startPageNo
     *            起始页，页面索引采用了和Java数组索引一致的方式，也就是首页的索引为0
     * @param maxResultsInPage
     *            页面所包含数据的记录数
     * @return
     */
    public IPage queryByPage(final String hql4Query,
                             final Map parameters,
                             final int startPageNo,
                             final int maxResultsInPage,
                             final String keyField) {
        return queryByPage(getHql4Count(hql4Query, keyField),
                           hql4Query,
                           parameters,
                           startPageNo,
                           maxResultsInPage,
                           false);
    }
    
    /**
     * 通用的分页查询,支持hql语句。使用者无需提供计数的hql，本方法会自动生成计数的hql，但是不适合复杂的hql（例如select的值带子查询）
     * 
     * @param hql4Query
     *            hql语句
     * @param parameters
     *            hql的参数
     * @param startPageNo
     *            起始页，页面索引采用了和Java数组索引一致的方式，也就是首页的索引为0
     * @param maxResultsInPage
     *            页面所包含数据的记录数
     * @return
     */
    public IPage queryByPage(final String hql4Query,
                             final Object[] parameters,
                             final int startPageNo,
                             final int maxResultsInPage,
                             final boolean enableArray2Map) {
        return queryByPage(getHql4Count(hql4Query),
                           hql4Query,
                           parameters,
                           startPageNo,
                           maxResultsInPage,
                           enableArray2Map);
    }
    
    /**
     * 通用的分页查询,支持hql语句。使用者无需提供计数的hql，本方法会自动生成计数的hql，但是不适合复杂的hql（例如select的值带子查询）
     * 
     * @param hql4Query
     *            hql语句
     * @param parameters
     *            hql的参数
     * @param startPageNo
     *            起始页，页面索引采用了和Java数组索引一致的方式，也就是首页的索引为0
     * @param maxResultsInPage
     *            页面所包含数据的记录数
     * @return
     */
    public IPage queryByPage(final String hql4Query,
                             final Object[] parameters,
                             final int startPageNo,
                             final int maxResultsInPage) {
        return queryByPage(getHql4Count(hql4Query),
                           hql4Query,
                           parameters,
                           startPageNo,
                           maxResultsInPage,
                           false);
    }
    
    private String getHql4Count(String hql4Query) {
        return getHql4Count(hql4Query, null);
    }
    
    /**
     * 由于hql不支持在from子句上的子查询，因此对于已知的查询语句，需要将查询语句中的select子句和order
     * by子句删除，并在前面添加计数用的select子句，以拼装成计数查询语句
     * 
     * @param hql4Query
     * @param counterFieldName
     * @return
     */
    private String getHql4Count(String hql4Query, String counterFieldName) {
        Assert.hasText(hql4Query, "用于查询的hql不能为空！");
        String selectCountClause = (StringUtils.isEmpty(counterFieldName)) ? "select count(*)  "
                                                                          : "select count(distinct " + counterFieldName
                                                                            + ") ";
        return selectCountClause + HibernateUtils.eraseSelectAndOrderByClause(hql4Query);
        // String hql4QueryLowerCased = hql4Query.toLowerCase();
        // int beginPos = hql4QueryLowerCased.indexOf("from");
        // Assert.isTrue(beginPos != -1, hql4Query + "无效，必须包含from关键字");
        // int orderByPos = hql4QueryLowerCased.indexOf("order by");
        // return selectCountClause + ((orderByPos < 0) ?
        // hql4Query.substring(beginPos)
        // : hql4Query.substring(beginPos,
        // orderByPos));
    }
}
